//********************************************************************************************************
// Product Name: MapWindow.dll Alpha
// Description:  Low level interfaces that allow separate components to use objects that are defined
//               in completely independant libraries so long as they satisfy the basic interface requirements.
//********************************************************************************************************
// The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); 
// you may not use this file except in compliance with the License. You may obtain a copy of the License at 
// http://www.mozilla.org/MPL/ 
//
// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF 
// ANY KIND, either express or implied. See the License for the specificlanguage governing rights and 
// limitations under the License. 
//
// The Original Code is MapWindow.Interfaces
//
// The Initial Developer of this Original Code is Ted Dunsford. Created in August, 2007.
// 
// Contributor(s): (Open source contributors should list themselves and their modifications here). 
// Modified to do 3D in January 2008 by Ted Dunsford
//********************************************************************************************************
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;

using MapWindow.Data;
using MapWindow.Geometries;
using MapWindow.Main;
using MapWindow.Main.Generic;

using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using MapWindow.Components.Map3D;

namespace MapWindow.Drawing
{
    /// <summary>
    /// This is a specialized MapFeatureLayer that specifically handles point drawing
    /// </summary>
    public class MapPointLayer : MapFeatureLayer, IMapPointLayer
    {
        #region Variable


        private Microsoft.DirectX.Direct3D.VertexBuffer _VertexBuffer;
        private Microsoft.DirectX.Direct3D.IndexBuffer _IndexBuffer;
        private int[] _Indices;
        private int _PointCount;
        private List<Vector3> _Points;
        private Texture _Texture;
        private bool _UseTexture;
        
        
        private Color _Color; // To be used if the texture isn't used

        private bool _Initialized;

        /// <summary>
        /// Gets or sets whether this layer has gone through the basic setup process.  Setting this to false
        /// will force it to recalculate things.
        /// </summary>
        public bool Initialized
        {
            get { return _Initialized; }
            set { _Initialized = value; }
        }

        
        private IPointSymbolizer _PointSymbolizer;

        private IEventList<IMapPoint> _MapPoints;

        #endregion


        #region Constructors

        /// <summary>
        /// Creates a new instance of a MapPointLayer without sending any status messages
        /// </summary>
        /// <param name="fl">The IFeatureLayer of data values to turn into a graphical MapPointLayer</param>
        public MapPointLayer(IFeatureLayer fl)
            : this(fl, null)
        {
            // this simply handles the default case where no status messages are requested
        }

        /// <summary>
        /// Creates a new instance of a MapPointLayer for storing and drawing points
        /// </summary>
        /// <param name="fl">Any implentation of an IFeatureLayer</param>
        /// <param name="progHandler">A valid implementation of the IProgHandler interface.</param>
        public MapPointLayer(IFeatureLayer fl, IProgHandler progHandler) : base(fl)
        {
            base._FeatureLayer = fl;
            _PointSymbolizer = new PointSymbolizer();
            _PointSymbolizer.BorderPen = new Pen(Color.Black);
            _PointSymbolizer.Shape = PointShapes.Ellipse;
            _PointSymbolizer.Size = new SizeF(Convert.ToSingle((fl.Envelope.Width / 100)),Convert.ToSingle( fl.Envelope.Width / 100));
            _PointSymbolizer.FillColor = Color.Green;
            _PointSymbolizer.Name = "Layer Symbology";
            _PointSymbolizer.ScaleMode = ScaleModes.Pixels;
            _FeatureSymbolizer = _PointSymbolizer;
            _MapPoints = new MapPointCollection(this);
            // Create enough levels to get it down to no more than about 100 points per level.
            
            if (fl.FeatureType != FeatureTypes.Point)
            {
                throw new ArgumentException("PointLayer should be created from a point feature");
            }

            _Points = new List<Vector3>();
            _Color = Color.Red;
            _UseTexture = true;

            // While technically this shouldn't take a noticeable timeframe, in extreme cases it might.
            ProgressMeter pm = new ProgressMeter(progHandler, "Caching float coordinates for " + this.Name, fl.Features.Count);
            int iFeature = 0;
            foreach (IFeature f in fl.Features)
            {
                
           
                // Multipoint

                for (int i = 0; i < f.BasicGeometry.NumGeometries; i++)
                {
                    IBasicGeometry bg = f.BasicGeometry.GetBasicGeometryN(i);
                    for (int iCoord = 0; iCoord < bg.NumPoints; iCoord++)
                    {
                        _Points.Add(new Vector3((float)bg.Coordinates[iCoord].X, (float)bg.Coordinates[iCoord].Y, 0f));
                            
                        
                    }
                }
                // Increment the progress meter and send messages only for integer changes of the percent.
                pm.CurrentValue = iFeature++;
            }
            _PointCount = _Points.Count;
            
            InitializeIndices(progHandler);
        }


        #endregion

        private void InitializeIndices(IProgHandler progHandler)
        {


            // Each block is created from 2 triangles.  Each block actually requires 6 indeces, but only 4 vertices.
            // If the upper right or lower left values are no-data, then only one of the triangles is created.
            //
            //    1    4 --- 5        Vertex arrangment:  0  1
            //     |\   \    |                            2  3
            //     | \   \   | 
            //     |  \   \  |       
            //    3 --- 2   6     
            //


            ProgressMeter pm = new ProgressMeter(progHandler, "Calculating index values for " + this.Name, _PointCount);
          
            _Indices = new int[_PointCount * 6];
            for (int pt = 0; pt < _PointCount; pt++)
            {
                _Indices[pt * 6] = pt * 4 + 0;
                _Indices[pt * 6 + 1] = pt * 4 + 3;
                _Indices[pt * 6 + 2] = pt * 4 + 2;
                _Indices[pt * 6 + 3] = pt * 4 + 0;
                _Indices[pt * 6 + 4] = pt * 4 + 1;
                _Indices[pt * 6 + 5] = pt * 4 + 3;
                pm.CurrentValue = pt;
            }
            pm.Reset();
        }

        void VertexBuffer_Created(object sender, EventArgs e)
        {
            VertexBuffer buffer = (VertexBuffer)sender;
            if (_UseTexture == true)
            {
                // Create a list of vertices equal to the number of items
                CustomVertex.PositionTextured[] verts = new CustomVertex.PositionTextured[_Points.Count * 4];
                // Randomly select the position of this vertex..  Keep it within a range of 5.
                for (int i = 0; i < _Points.Count; i++)
                {
                    // Top Left
                    verts[i * 4].Position = new Vector3(_Points[i].X - _PointSymbolizer.Size.Width / 2, _Points[i].Y + _PointSymbolizer.Size.Height / 2, 0f);
                    verts[i * 4].Tu = 0f;
                    verts[i * 4].Tv = 0f;

                    // Top Right
                    verts[i * 4 + 1].Position = new Vector3(_Points[i].X + _PointSymbolizer.Size.Width / 2, _Points[i].Y + _PointSymbolizer.Size.Height / 2, 0f);
                    verts[i * 4 + 1].Tu = 1f;
                    verts[i * 4 + 1].Tv = 0f;

                    // Bottom Left
                    verts[i * 4 + 2].Position = new Vector3(_Points[i].X - _PointSymbolizer.Size.Width / 2, _Points[i].Y - _PointSymbolizer.Size.Height / 2, 0f);
                    verts[i * 4 + 2].Tu = 0f;
                    verts[i * 4 + 2].Tv = 1f;

                    // Bottom Right
                    verts[i * 4 + 3].Position = new Vector3(_Points[i].X + _PointSymbolizer.Size.Width / 2, _Points[i].Y - _PointSymbolizer.Size.Height / 2, 0f);
                    verts[i * 4 + 3].Tu = 1f;
                    verts[i * 4 + 3].Tv = 1f;
                }
                buffer.SetData(verts, 0, LockFlags.None);
            }
            else
            {
                CustomVertex.PositionColored[] verts = new CustomVertex.PositionColored[_Points.Count * 4];
                // Randomly select the position of this vertex..  Keep it within a range of 5.
                for (int i = 0; i < _Points.Count; i++)
                {
                    // Top Left
                    verts[i * 4].Position = new Vector3(_Points[i].X - _PointSymbolizer.Size.Width / 2, _Points[i].Y + _PointSymbolizer.Size.Height / 2, 0f);
                    verts[i * 4].Color = _Color.ToArgb();
                   
                    // Top Right
                    verts[i * 4 + 1].Position = new Vector3(_Points[i].X + _PointSymbolizer.Size.Width / 2, _Points[i].Y + _PointSymbolizer.Size.Height / 2, 0f);
                    verts[i * 4 + 1].Color = _Color.ToArgb();

                    // Bottom Left
                    verts[i * 4 + 2].Position = new Vector3(_Points[i].X - _PointSymbolizer.Size.Width / 2, _Points[i].Y - _PointSymbolizer.Size.Height / 2, 0f);
                    verts[i * 4 + 2].Color = _Color.ToArgb();

                    // Bottom Right
                    verts[i * 4 + 3].Position = new Vector3(_Points[i].X + _PointSymbolizer.Size.Width / 2, _Points[i].Y - _PointSymbolizer.Size.Height / 2, 0f);
                    verts[i * 4 + 3].Color = _Color.ToArgb();
                }
                buffer.SetData(verts, 0, LockFlags.None);
            }
           
            

        }

        private void Initialize(Device device)
        {

            if (_UseTexture == true)
            {
                // Manufacture a point texture

                _Texture = _PointSymbolizer.CreateTexture(device);
            
                _VertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionTextured), _Points.Count * 4, device, Usage.Dynamic | Usage.WriteOnly, CustomVertex.PositionTextured.Format, Pool.Default);
            }
            else
            {
                _VertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionColored), _Points.Count * 4, device, Usage.Dynamic | Usage.WriteOnly, CustomVertex.PositionColored.Format, Pool.Default);
            }
                
            _VertexBuffer.Created += new EventHandler(VertexBuffer_Created);
            VertexBuffer_Created(_VertexBuffer, null);

            // create IndexBuffer to store indices into vertex data
            _IndexBuffer = new IndexBuffer(typeof(int), _Indices.Length, device, Usage.WriteOnly, Pool.Default);
            _IndexBuffer.Created += new EventHandler(this.OnIndexBufferCreate);
            OnIndexBufferCreate(_IndexBuffer, null);
            _Initialized = true;
        }

        private void OnIndexBufferCreate(object sender, EventArgs e)
        {
            _IndexBuffer.SetData(_Indices, 0, LockFlags.None);

        }

        /// <summary>
        /// Creates an image using the symbol properties and returns an appropriate texture.
        /// </summary>
        /// <param name="device">The Microsoft.DirectX.Direct3D device for controlling texture creation</param>
        /// <returns>A Texture that should represent the symbol for this icon</returns>
        public Texture CreateTexture(Device device)
        {
            return _PointSymbolizer.CreateTexture(device);
        }


       
       

        /// <summary>
        /// Draws the graphics from the point layer onto the 3D Device
        /// </summary>
        /// <param name="device">A Direct3D device</param>
        public override void Draw(Device device)
        {
            if (Visible == false)
            {
                OnAfterDrawing(device, true, null);
                return;
            }

            if (OnBeforeDrawing(device) == true) // Draw Canceled
            {
                OnAfterDrawing(device, true, null);
                return;
            }

            try
            {
               
                if (_Initialized == false)
                {
                    Initialize(device);
                }
                if (_PointCount > 0)
                {
                    
                    //device.RenderState.PointSize = 10f;
                    //device.VertexFormat = CustomVertex.PositionTextured.Format;
                    //device.SetStreamSource(0, _VetexBuffer, 0);
                    //device.DrawPrimitives(PrimitiveType.PointList, 0, _PointCount);
                    if (_UseTexture == true)
                    {
                        // turn on transparent texture handling
                        device.TextureState[0].ColorOperation = TextureOperation.Modulate;
                        device.TextureState[0].ColorArgument0 = TextureArgument.Diffuse;
                        device.TextureState[0].ColorArgument1 = TextureArgument.TextureColor;
                        device.TextureState[0].AlphaOperation = TextureOperation.Modulate;
                        device.TextureState[0].AlphaArgument0 = TextureArgument.Diffuse;
                        device.TextureState[0].AlphaArgument1 = TextureArgument.TextureColor;
                        device.SetTexture(0, _Texture);
                        device.RenderState.SourceBlend = Blend.SourceAlpha;
                        device.RenderState.DestinationBlend = Blend.InvSourceAlpha;
                        device.RenderState.AlphaBlendEnable = true;

                        // Specify to use textures on the vertices
                        device.VertexFormat = CustomVertex.PositionTextured.Format;

                        // Specify the data source to obtain the vertex locations for triangles
                        device.SetStreamSource(0, _VertexBuffer, 0);

                        // Specify the data source with the index values that specify the order to add the vertex locations to make triangles
                        device.Indices = _IndexBuffer;

                        // Actually draw the triangles
                        device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, _PointCount * 4, 0, _Indices.Length / 3);

                        // turn off the transparent texture handling
                        device.RenderState.AlphaBlendEnable = false;

                    }
                    else
                    {
                        device.VertexFormat = CustomVertex.PositionColored.Format;
                        device.SetStreamSource(0, _VertexBuffer, 0);
                        device.Indices = _IndexBuffer;
                        device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, _PointCount * 4, 0, _Indices.Length / 3);
                    }
                }
            }
            catch (Exception ex)
            {
                OnAfterDrawing(device, false, ex);
            }
        }

        /// <summary>
        /// Allows the object to dispose the unmannaged memory components.
        /// </summary>
        public override void Dispose()
        {
            if(_Texture != null)_Texture.Dispose();
            if(_IndexBuffer!=null)_IndexBuffer.Dispose();
            if(_VertexBuffer != null) _VertexBuffer.Dispose();
        }



        #region Methods

        /// <summary>
        /// This will systematically clear all the layer properties as well
        /// as looping through and clearing all of the individual point characteristics
        /// </summary>
        public new void Clear()
        {
      
            _MapPoints = new MapPointCollection(this);
            
        }

        /// <summary>
        /// resets all the mappoints to use their default drawing characteristics.
        /// </summary>
        public void ClearIndividuals()
        {
            _MapPoints = new MapPointCollection(this);

        }

        /// <summary>
        /// Copies all the point symbol properties from any valid implementation of IPointFeatureSymbology
        /// like a MapPoint or a MapPointLayer 
        /// </summary>
        /// <param name="copyFrom"></param>
        public void Copy(IPointSymbolizer copyFrom)
        {
            _PointSymbolizer.Copy(copyFrom);
        }

        
        /// <summary>
        /// This simply calls the draw function for each of its specific features, if it has them defined,
        /// or else it calls the default characteristics.
        /// TO DO: Support Symbols based on attributes
        /// </summary>
        /// <param name="g">The System.Drawing.Graphics surface to draw on</param>
        /// <param name="clipRectangle">chops off the drawing at these bounds</param>
        /// <param name="target">The region to fill with the layer</param>
        public override void Draw(System.Drawing.Graphics g, System.Drawing.Rectangle clipRectangle, System.Drawing.Rectangle target)
        {
            _IsBusy = true;
            try
            {
                System.Drawing.Drawing2D.Matrix mat;
                // We can't draw without a graphics surface
                if (g == null)
                {
                    OnPaintCancelled(new ArgumentNullException("g cannot be null."), g, Rectangle.Empty, Rectangle.Empty);
                    return;
                }
                if (g.Transform == null)
                {
                    mat = new System.Drawing.Drawing2D.Matrix();
                }
                else
                {
                    mat = (System.Drawing.Drawing2D.Matrix)g.Transform.Clone();
                }

                System.Drawing.Drawing2D.Matrix oldMatrix = g.Transform.Clone();

                // Obviously don't draw the layer if it isn't supposed to be visible
                if (this.Visible == false)
                {
                    OnPaintCancelled(new OperationCanceledException("Visible was false"), g, clipRectangle, target);
                    return;
                }

                // The OnBeforePainting method can cancel the drawing
                if (OnBeforePainting(g, clipRectangle, target) == true)
                {
                    OnPaintCancelled(new OperationCanceledException("Canceled by OnBeforePainting"), g, clipRectangle, target);
                    return;
                }

                // Usually clipping is done at the MapFrame level, but custom users should be able to draw the layer too
                if (clipRectangle.IsEmpty == false)
                {
                    // use the intersection of any clipping that has already been applied and the specified rectangle
                    g.IntersectClip(clipRectangle);
                }

             

                // Create a Transform if a target exists, otherwise, just use the existing transform
                if (target.IsEmpty == false)
                {
                    // Before applying any transforms, modify the clip region in the previous transform environment
                    g.IntersectClip(target);

                    // Initialize a new transform that will scale the image to the target
                    mat = new System.Drawing.Drawing2D.Matrix();

                    // While we are in pixel coordinates, translate to the specified Frame
                    mat.Translate((float)target.X, (float)target.Y);

                    // Scaling 
                    // An envelope width or height of 0 will throw an exception.
                    // I think that is better than simply not drawing, since it helps track the problem
                    float xScale = (float)(target.Width / Envelope.Width);
                    float yScale = (float)(target.Height / Envelope.Height);
                    float maxScale = Math.Min(xScale, yScale);

                    // By using the maxScale for both, we will preserve the aspect ratio, but fit the image
                    // completely into the view.  We also invert Y to account for geographic coordinates
                    // generally having Y get larger as you go up.
                    mat.Scale(maxScale, -maxScale);

                    // If a target is specified, we have to throw out the expectation that the draw instruction is
                    // coming from a view frame and instead try to fit the entire layer into the specified frame,
                    // while still preserving the aspect ratio.
                    mat.Translate((float)-Envelope.MinX, -(float)Envelope.MaxY);
                    
                    // I control the transform this way because I can't seem to apply methods to the 
                    // graphics transform directly.
                    g.Transform = mat;

                }


                IEnvelope view = new Envelope((double)g.ClipBounds.X, (double)g.ClipBounds.Right, (double)g.ClipBounds.Y, (double)g.ClipBounds.Bottom);
                if (this.Envelope.Intersects(view))
                {
                    g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
                    System.Drawing.Drawing2D.Matrix oldPosition = (System.Drawing.Drawing2D.Matrix)g.Transform.Clone();
                   
                  
                    foreach( IMapPoint mp in _MapPoints)
                    {
                        if (view.Contains(mp) == false) continue;
                        
                        if (_CancelDrawing == true) // Check this each time to bail as quickly as possible
                        {
                            OnPaintCancelled(new OperationCanceledException("CancelDrawing was true"), g, clipRectangle, target);
                        }

                        mat = (System.Drawing.Drawing2D.Matrix)oldPosition.Clone();
                        //mat.Translate(-this.MapPoints[shp].Position.X, -this.MapPoints[shp].Position.Y); // Translate to the location of the point
                        mat.Translate((float)mp.X, (float)mp.Y);
                        // Flip back to the normal orientation after translation, but before drawing images
                        mat.Multiply(new System.Drawing.Drawing2D.Matrix(1f, 0f, 0f, -1f, 0f, 0f));

                        g.Transform = mat;

                        // g now already contains all the necessary information from target or clipRectangle
                        mp.Draw(g, Rectangle.Empty, Rectangle.Empty);

                    }


                    g.Transform = oldMatrix;
                }

                
            }
            catch (Exception ex)
            {
                
                // Only throw the event if this method was called directly, otherwise, pass up the exception
                OnPaintCancelled(ex, g, clipRectangle, target);
              
            }
            
            OnAfterPainting(g, clipRectangle, target);
        }

        /// <summary>
        /// NotImplemented
        /// </summary>
        /// <param name="FID"></param>
        /// <returns></returns>
        public IMapPoint GetPointFeature(int FID)
        {
            throw new NotImplementedException("The method or operation is not implemented.");
        }

      

        #endregion


        #region Properties

        /// <summary>
        /// Gets or sets the default float angle, measured counter-clockwise from horizontal - east.
        /// This will not alter the individual settings of specific MapPoints
        /// </summary>
        public float Angle
        {
            get { return _PointSymbolizer.Angle; }
            set { _PointSymbolizer.Angle = value; }

        }
      
        /// <summary>
        /// Gets or sets the default color for drawing the dots at the center of points for this layer.
        /// </summary>
        public System.Drawing.Color DotColor
        {
            get { return _PointSymbolizer.DotColor; }
            set { _PointSymbolizer.DotColor = value; }
        }

        /// <summary>
        /// Gets or sets the default float radius determining how big the dots at the center of the points are
        /// for this layer.
        /// </summary>
        public float DotRadius
        {
            get { return _PointSymbolizer.DotRadius; }
            set { _PointSymbolizer.DotRadius = value; }
        }

        /// <summary>
        /// A boolean value determining whether or not to draw points on this layer.
        /// </summary>
        public bool DotVisible
        {
            get { return _PointSymbolizer.DotVisible; }
            set { _PointSymbolizer.DotVisible = value; }
        }

        /// <summary>
        /// Gets an enumeration specifying Point because all MapPointLayers contain Point features
        /// </summary>
        public override FeatureTypes FeatureType
        {
            get
            {
                return FeatureTypes.Point;
            }
        }

        /// <summary>
        /// Gets or sets the FeatureSymbolizer determining the shared properties.  This is actually still the PointSymbolizer
        /// and should not be used directly on Polygons or Lines.
        /// </summary>
        /// <exception cref="System.InvalidOperationException">Unable to assign a non-point symbolizer to a MapPointLayer</exception>
        public override IFeatureSymbolizer FeatureSymbolizer
        {
            get
            {
                return _PointSymbolizer;
            }
            set
            {
                if (value is IPointSymbolizer)
                {
                    _PointSymbolizer = (IPointSymbolizer)value;
                }
                else
                {
                    throw new InvalidOperationException("Unable to assign a non-point symbolizer to a MapPointLayer");
                }
                
            }
        }

        /// <summary>
        /// Before drawing to the real world, a sprite is created and used as a texture.
        /// This controls the number of pixels that are on the texture, which will be
        /// stretched in order to fit the real world dimensions.
        /// </summary>
        public System.Drawing.Size PixelSize
        {
            get { return _PointSymbolizer.PixelSize; }
            set { _PointSymbolizer.PixelSize = value; }
        }


        /// <summary>
        /// Gets or sets the default symbolizer for points on this layer.
        /// </summary>
        public IPointSymbolizer PointSymbolizer
        {
            get
            {
                return _PointSymbolizer;
            }
            set
            {
                _PointSymbolizer = value;
            }

        }
        

        /// <summary>
        /// Gets or sets the System.Drawing.Brush to be used when filling this point.
        /// Setting this value will also change the FillColor property, but only if
        /// the brush is a System.Drawing.SolidBrush.  Otherwise, the color property
        /// will be ignored.
        /// </summary>
        public System.Drawing.Icon Icon
        {
            get { return _PointSymbolizer.Icon; }
            set { _PointSymbolizer.Icon = value; }

        }

        /// <summary>
        /// Gets or sets the default Image to be used when drawing points for this layer.
        /// This will only be used if the PointShapes
        /// </summary>
        public System.Drawing.Image Image
        {
            get { return _PointSymbolizer.Image; }

            set { _PointSymbolizer.Image = value; }
        }


        /// <summary>
        /// Gets or sets the collection of MapPoints that exists within this object.
        /// </summary>
        public virtual IEventList<IMapPoint> MapPoints
        {
            get { return _MapPoints; }
            set { _MapPoints = value; }
        }
        
        /// <summary>
        /// Gets or sets the default shape for the points on this layer
        /// </summary>
        public PointShapes Shape
        {
            get { return _PointSymbolizer.Shape; }
            set { _PointSymbolizer.Shape = value; }
        }

        /// <summary>
        /// Gets or sets the default size for the points on this layer
        /// </summary>
        public System.Drawing.SizeF Size
        {
            get { return _PointSymbolizer.Size; }
            set { _PointSymbolizer.Size = value; }
        }

        
      

        #endregion

       
    }
}
